
 #ifndef CHIP_PLL_H_INCLUDED
 #define CHIP_PLL_H_INCLUDED

 #ifdef __cplusplus
 extern "C" {
 #endif

 // Bugzilla #11801
 #ifdef AVR32_SCIF_PLL_VCO_RANGE0_MAX_FREQ
 #undef AVR32_SCIF_PLL_VCO_RANGE0_MAX_FREQ
 #endif
 #define AVR32_SCIF_PLL_VCO_RANGE0_MAX_FREQ   240000000
 #ifdef AVR32_SCIF_PLL_VCO_RANGE0_MIN_FREQ
 #undef AVR32_SCIF_PLL_VCO_RANGE0_MIN_FREQ
 #endif
 #define AVR32_SCIF_PLL_VCO_RANGE0_MIN_FREQ   160000000
 #ifdef AVR32_SCIF_PLL_VCO_RANGE1_MAX_FREQ
 #undef AVR32_SCIF_PLL_VCO_RANGE1_MAX_FREQ
 #endif
 #define AVR32_SCIF_PLL_VCO_RANGE1_MAX_FREQ   180000000
 #ifdef AVR32_SCIF_PLL_VCO_RANGE1_MIN_FREQ
 #undef AVR32_SCIF_PLL_VCO_RANGE1_MIN_FREQ
 #endif
 #define AVR32_SCIF_PLL_VCO_RANGE1_MIN_FREQ   80000000

 #define PLL_MAX_STARTUP_CYCLES    ((1 << AVR32_SCIF_PLL_PLLCOUNT_SIZE) - 1)
 #define NR_PLLS                   2

 #define PLL_TIMEOUT_MS    \
     div_ceil(1000 * (PLL_MAX_STARTUP_CYCLES * 2), OSC_RCSYS_MIN_HZ)

 #define PLL_MIN_HZ                40000000
 #define PLL_MAX_HZ                240000000



 #define PLL_OPT_VCO_RANGE_LOW     0

 #define PLL_OPT_OUTPUT_DIV        1

 #define PLL_OPT_WBM_DISABLE       2

 #define PLL_NR_OPTIONS            AVR32_SCIF_PLL_PLLOPT_SIZE

 #define PLL_VCO_LOW_THRESHOLD     ((AVR32_SCIF_PLL_VCO_RANGE0_MIN_FREQ         \
         + AVR32_SCIF_PLL_VCO_RANGE1_MAX_FREQ) / 2)


 #ifndef __ASSEMBLY__

 #include <assert.h>
 #include <avr32/io.h>
 #include <osc.h>
 #include <stdbool.h>
 #include <stdint.h>

 enum pll_source {
     PLL_SRC_OSC0            = 0,   
     PLL_NR_SOURCES,                
 };

 struct pll_config {
     uint32_t ctrl;
 };

 #define pll_get_default_rate(pll_id)                                           \
     ((osc_get_rate(CONFIG_PLL##pll_id##_SOURCE)                            \
             * CONFIG_PLL##pll_id##_MUL)                            \
             / CONFIG_PLL##pll_id##_DIV)

 static inline void pll_config_set_option(struct pll_config *cfg,
         unsigned int option)
 {
     Assert(option < PLL_NR_OPTIONS);

     cfg->ctrl |= 1U << (AVR32_SCIF_PLLOPT + option);
 }

 static inline void pll_config_clear_option(struct pll_config *cfg,
         unsigned int option)
 {
     Assert(option < PLL_NR_OPTIONS);

     cfg->ctrl &= ~(1U << (AVR32_SCIF_PLLOPT + option));
 }

 static inline void pll_config_init(struct pll_config *cfg,
         enum pll_source src, unsigned int div, unsigned int mul)
 {
     uint32_t vco_hz;

     Assert(src < PLL_NR_SOURCES);

     /* Calculate internal VCO frequency */
     vco_hz = osc_get_rate(src) * mul;
     vco_hz /= div;
     Assert(vco_hz >= PLL_MIN_HZ);
     Assert(vco_hz <= PLL_MAX_HZ);

     cfg->ctrl = 0;

     /* Bring the internal VCO frequency up to the minimum value */
     if ((vco_hz < PLL_MIN_HZ * 2) && (mul <= 8)) {
         mul *= 2;
         vco_hz *= 2;
         pll_config_set_option(cfg, PLL_OPT_OUTPUT_DIV);
     }

     /* Set VCO frequency range according to calculated value */
     if (vco_hz < PLL_VCO_LOW_THRESHOLD)
         pll_config_set_option(cfg, PLL_OPT_VCO_RANGE_LOW);

     Assert(mul > 2 && mul <= 16);
     Assert(div > 0 && div <= 15);

     cfg->ctrl |= ((mul - 1) << AVR32_SCIF_PLLMUL)
         | (div << AVR32_SCIF_PLLDIV)
         | (PLL_MAX_STARTUP_CYCLES << AVR32_SCIF_PLLCOUNT)
         | (src << AVR32_SCIF_PLLOSC);
 }

 #define pll_config_defaults(cfg, pll_id)                                       \
     pll_config_init(cfg,                                                   \
             CONFIG_PLL##pll_id##_SOURCE,                           \
             CONFIG_PLL##pll_id##_DIV,                              \
             CONFIG_PLL##pll_id##_MUL)

 static inline void pll_config_read(struct pll_config *cfg, unsigned int pll_id)
 {
     Assert(pll_id < NR_PLLS);

     cfg->ctrl = AVR32_SCIF.pll[pll_id];
 }

 extern void pll_config_write(const struct pll_config *cfg, unsigned int pll_id);
 extern void pll_enable(const struct pll_config *cfg, unsigned int pll_id);
 extern void pll_disable(unsigned int pll_id);

 static inline bool pll_is_locked(unsigned int pll_id)
 {
     Assert(pll_id < NR_PLLS);

     return !!(AVR32_SCIF.pclksr & (1U << (AVR32_SCIF_PLL0_LOCK + pll_id)));
 }

 static inline void pll_enable_source(enum pll_source src)
 {
     switch (src) {
     case PLL_SRC_OSC0: /* Fall through */
         osc_enable(src);
         osc_wait_ready(src);
         break;

     default:
         // unhandled_case(src);
         break;
     }
 }

 static inline void pll_enable_config_defaults(unsigned int pll_id)
 {
     struct pll_config pllcfg;

     if (pll_is_locked(pll_id)) {
         return; // Pll already running
     }
     switch (pll_id) {
 #ifdef CONFIG_PLL0_SOURCE
     case 0:
         pll_enable_source(CONFIG_PLL0_SOURCE);
         pll_config_init(&pllcfg,
                 CONFIG_PLL0_SOURCE,
                 CONFIG_PLL0_DIV,
                 CONFIG_PLL0_MUL);
         break;
 #endif
 #ifdef CONFIG_PLL1_SOURCE
     case 1:
         pll_enable_source(CONFIG_PLL1_SOURCE);
         pll_config_init(&pllcfg,
                 CONFIG_PLL1_SOURCE,
                 CONFIG_PLL1_DIV,
                 CONFIG_PLL1_MUL);
         break;
 #endif
     default:
         Assert(false);
         break;
     }
     pll_enable(&pllcfg, pll_id);
     while (!pll_is_locked(pll_id));
 }

 #endif /* __ASSEMBLY__ */


 #ifdef __cplusplus
 }
 #endif

 #endif /* CHIP_PLL_H_INCLUDED */
